import wx, keyword, os
from editors.editor import Editor
from editors import register_opener, register_factory, EditorFactoryInfo
import wx.stc as stc
from util import *

class CodeEditor(Editor):
	def __init__(self, parent, style = 0, path = None, lexer = None):
		"""
		Creates a new CodeEditor with the given parent widget and style,
		editing the given path using the given lexer ID.  If no lexer is given,
		the default Python lexer is used.
		"""
		self.items = ()
		self.items_added = False

		super(CodeEditor, self).__init__(parent, style)
		self.path = path

		# Set up UI
		self.stc = PythonTextEditor(self, -1)
		if lexer: self.stc.SetLexer(lexer)
		s = wx.BoxSizer(wx.HORIZONTAL)
		s.Add(self.stc, 1, wx.ALL | wx.EXPAND)
		self.SetSizer(s)
		self.Layout()

		# Add line numbers
		self.stc.SetMarginType(1, stc.STC_MARGIN_NUMBER)
		# TODO: vary margin size depending on number of lines
		self.stc.SetMarginWidth(1, 40)

		# Load text
		if self.path is not None:
			# TODO: handle nonexistent file
			with open(self.path) as f:
				self.stc.SetText(f.read())
			self.title = os.path.basename(self.path)
			self.modified = False
			self.stc.EmptyUndoBuffer()

		# Keep track of modification state
		self.stc.SetSavePoint()
		self.stc.Bind(stc.EVT_STC_SAVEPOINTLEFT, self.on_modify)
		self.stc.Bind(stc.EVT_STC_SAVEPOINTREACHED, self.on_unmodify)

		# Perform really basic code folding
		self.stc.Bind(stc.EVT_STC_MARGINCLICK, self.on_margin_click)
	
	def on_margin_click(self, event):
		"Handles events where the user clicks in the margin."
		if event.GetMargin() != 2: return
		line = self.stc.LineFromPosition(event.GetPosition())
		self.stc.ToggleFold(line)
	
	def on_unmodify(self, event): self.modified = False
	def on_modify(self, event): self.modified = True

	def save(self, save_as = False):
		# Get filename to save from the user.
		if self.path is None or save_as:
			# FIXME: this clears out the existing path on Save As if the
			# user cancels
			self.path = self.parent.show_file_dialog(wildcard =
			                                         'Python Files (*.py)|*.py')
			if self.path is None: return False
			self.title = os.path.basename(self.path)

		# Save the file.
		with open(self.path, 'w') as f:
			f.write(self.stc.GetText())

		# Mark the file as clean
		self.stc.SetSavePoint()

		return True

# Default fold marker styles
# Adapted from the wxPython StyledTextCtrl_2 demo (wxPython 2.8.11.0).
DEFAULT_FOLDING_STYLE = {
	stc.STC_MARKNUM_FOLDER        : stc.STC_MARK_BOXPLUS,
	stc.STC_MARKNUM_FOLDEROPEN    : stc.STC_MARK_BOXMINUS,
	stc.STC_MARKNUM_FOLDERSUB     : stc.STC_MARK_VLINE,
	stc.STC_MARKNUM_FOLDEROPENMID : stc.STC_MARK_BOXMINUSCONNECTED,
	stc.STC_MARKNUM_FOLDERMIDTAIL : stc.STC_MARK_TCORNER,
	stc.STC_MARKNUM_FOLDERTAIL    : stc.STC_MARK_LCORNER,
	stc.STC_MARKNUM_FOLDEREND     : stc.STC_MARK_BOXPLUSCONNECTED,
}

# Default Python text styles
# Adapted from the wxPython StyledTextCtrl_2 demo (wxPython 2.8.11.0).
PYTHON_TEXT_STYLES = {
	# Global text styles
	stc.STC_STYLE_BRACEBAD    : "fore:#000000,face:%(mono)s,back:#FF0000,bold",
	stc.STC_STYLE_BRACELIGHT  : "fore:#FFFFFF,face:%(mono)s,back:#0000FF,bold",
	stc.STC_STYLE_CONTROLCHAR : "face:%(mono)s",
	stc.STC_STYLE_DEFAULT     : "face:%(mono)s,size:%(size)d",
	stc.STC_STYLE_LINENUMBER  : "back:#9AAAC0,face:%(mono)s,size:%(size2)d",

	# Python-specific text styles
	stc.STC_P_CHARACTER       : "fore:#7F007F,face:%(mono)s,size:%(size)d",
	stc.STC_P_CLASSNAME       : "fore:#0000FF,face:%(mono)s,bold,underline,size:%(size)d",
	stc.STC_P_COMMENTBLOCK    : "fore:#7F7F7F,face:%(mono)s,size:%(size)d",
	stc.STC_P_COMMENTLINE     : "fore:#007F00,face:%(mono)s,size:%(size)d",
	stc.STC_P_DEFAULT         : "fore:#000000,face:%(mono)s,size:%(size)d",
	stc.STC_P_DEFNAME         : "fore:#007F7F,face:%(mono)s,bold,size:%(size)d",
	stc.STC_P_IDENTIFIER      : "fore:#000000,face:%(mono)s,size:%(size)d",
	stc.STC_P_NUMBER          : "fore:#007F7F,face:%(mono)s,size:%(size)d",
	stc.STC_P_OPERATOR        : "bold,size:%(size)d,face:%(mono)s",
	stc.STC_P_STRING          : "fore:#7F007F,face:%(mono)s,size:%(size)d",
	stc.STC_P_STRINGEOL       : "fore:#000000,face:%(mono)s,back:#E0C0E0,eol,size:%(size)d",
	stc.STC_P_TRIPLE          : "fore:#7F0000,face:%(mono)s,size:%(size)d",
	stc.STC_P_TRIPLEDOUBLE    : "fore:#7F0000,face:%(mono)s,size:%(size)d",
	stc.STC_P_WORD            : "fore:#00007F,face:%(mono)s,bold,size:%(size)d",
}

# Default font sizes and faces for the editor.
# Adapted from the wxPython StyledTextCtrl_2 demo (wxPython 2.8.11.0).
if wx.Platform == '__WXMAC__':
	DEFAULT_FACES = {
		'arial' : 'Arial',
		'mono'  : 'Monaco',
		'size'  : 12,
		'size2' : 10,
	}
elif wx.Platform == '__WXMSW__':
	DEFAULT_FACES = {
		'arial' : 'Arial',
		'mono'  : 'Courier New',
		'size'  : 10,
		'size2' : 8,
	}
else:
	DEFAULT_FACES = {
		'arial' : 'Helvetica',
		'mono'  : 'Courier',
		'size'  : 12,
		'size2' : 10,
	}

class PythonEditorStyle(object):
	"""
	Applies style properties to the Python text editor.
	Adapted from the wxPython demo.
	"""
	def __init__(self,
	             folding_style      = DEFAULT_FOLDING_STYLE,
	             folding_foreground = 'white',
	             folding_background = '#808080',
	             font_faces         = DEFAULT_FACES,
	             text_styles        = PYTHON_TEXT_STYLES):
		self.folding_style      = folding_style
		self.folding_foreground = folding_foreground
		self.folding_background = folding_background
		self.font_faces         = font_faces
		self.text_styles        = text_styles

	def apply_to_stc(self, text):
		text.SetCaretForeground("blue")

		text.SetProperty("tab.timmy.whinge.level", "1")
		text.SetMargins(0,0)

		text.SetViewWhiteSpace(False)
		text.SetEdgeMode(stc.STC_EDGE_BACKGROUND)
		text.SetEdgeColumn(78)

		self.apply_text_styles(text)
		self.apply_marker_styles(text)

	def apply_marker_styles(self, text):
		for marker_style, style_choice in self.folding_style.iteritems():
			text.MarkerDefine(marker_style, style_choice,
			                  self.folding_foreground,
			                  self.folding_background)

		text.SetProperty("fold", "1")

		# Setup a margin to hold fold markers
		text.SetMarginType(2, stc.STC_MARGIN_SYMBOL)
		text.SetMarginMask(2, stc.STC_MASK_FOLDERS)
		text.SetMarginSensitive(2, True)
		text.SetMarginWidth(2, 12)

	def apply_text_styles(self, text):
		text.StyleSetSpec(stc.STC_STYLE_DEFAULT,
		                  self.text_styles[stc.STC_STYLE_DEFAULT])
		text.StyleClearAll()
		text.StyleSetSpec(stc.STC_STYLE_DEFAULT,
		                  self.text_styles[stc.STC_STYLE_DEFAULT])
		for style, style_spec in self.text_styles.iteritems():
			if style == stc.STC_STYLE_DEFAULT: continue
			text.StyleSetSpec(style, style_spec % self.font_faces)

class PythonTextEditor(stc.StyledTextCtrl):
	"""
	Styled text control from the wxPython demo.
	"""

	def __init__(self, parent, ID,
				 pos=wx.DefaultPosition, size=wx.DefaultSize,
				 style=0, editor_style=None):
		stc.StyledTextCtrl.__init__(self, parent, ID, pos, size, style)

		self.SetLexer(stc.STC_LEX_PYTHON)
		self.SetKeyWords(0, " ".join(keyword.kwlist))

		# Apply styles to editor
		if editor_style is None:
			editor_style = PythonEditorStyle()
		editor_style.apply_to_stc(self)

		# TODO: handle events
		# TODO: support autocomplete

def create_python_code_file(app):
	CodeEditor(app.main_win)

# Maps extensions to lexers used for highlighting them.
# Most of these lexers probably won't be needed, but we get the functionality
# for free anyway from the StyledTextCtrl.
EXTENSIONS_TO_LEXERS = {
	'bat'   : stc.STC_LEX_BATCH,
	'c'     : stc.STC_LEX_CPP,
	'cpp'   : stc.STC_LEX_CPP,
	'diff'  : stc.STC_LEX_DIFF,
	'h'     : stc.STC_LEX_CPP,
	'hpp'   : stc.STC_LEX_CPP,
	'htm'   : stc.STC_LEX_HTML,
	'html'  : stc.STC_LEX_HTML,
	'patch' : stc.STC_LEX_DIFF,
	'py'    : stc.STC_LEX_PYTHON,
	'pyw'   : stc.STC_LEX_PYTHON,
	'rb'    : stc.STC_LEX_RUBY,
	'sql'   : stc.STC_LEX_SQL,
	'xml'   : stc.STC_LEX_HTML,
}

# Some files have no extension, so we infer their highlighting from their
# file name.
FILENAMES_TO_LEXERS = {
	'Makefile' : stc.STC_LEX_MAKEFILE
}

def open_code_file(app, path):
	"""
	Opens any file, attempting to use the appropriate lexer.
	"""
	file_name, file_ext = os.path.splitext(path)
	file_ext = file_ext[1:]
	lexer = EXTENSIONS_TO_LEXERS.get(file_ext, stc.STC_LEX_NULL)
	if lexer == stc.STC_LEX_NULL:
		lexer = FILENAMES_TO_LEXERS.get(file_ext, stc.STC_LEX_NULL)
	CodeEditor(app.main_win, path=path, lexer=lexer)
	return True

# TODO: switch to using file types to identify open-able files.
# NOTE: ensure that code.py is included last in __init__.py, because
# this opener will open ALL files!
register_opener(open_code_file)
register_factory(EditorFactoryInfo('New Python File', 'code', create_python_code_file))
